package experiments.tutoriel;

import java.io.File;
import java.io.IOException;
import java.util.Iterator;

import algorithm.classification.binary.PerceptronLoss_L2Regularized;
import algorithm.classification.binary.parameters.PerceptronParameters;
import algorithm.classification.evaluation.BinaryClassifierEvaluation;
import algorithm.classification.functionsandloss.HingeLoss;
import tools.Pair;
import data.dictionary.Dictionary_Mapper;
import data.dictionary.Dictionary_Memory;
import data.dictionary.builder.DictionaryBuilderFromFile;
import data.dictionary.builder.DictionaryBuilderFromOfflineSet;
import data.dictionary.saver.DictionarySaver;
import data.factory.DocumentDirectoryOfflineSetFactory;
import data.factory.NumericalOfflineSetFromOctaveFile;
import data.factory.OctaveLoader;
import data.mapper.Mapper;
import data.output.OctaveSaver;
import data.preprocessing.StringProcessor;
import data.preprocessing.StringProcessor_FromStringProcessors;
import data.preprocessing.StringProcessor_LowerCase;
import data.preprocessing.StringProcessor_RemoveLineAndSpace;
import data.preprocessing.StringProcessor_RemovePunctuation;
import data.preprocessing.StringProcessor_TreeTagger;
import data.set.OfflineSet;
import data.set.OfflineSetIterator;
import data.set.adaptator.OfflineSet_Mapper;
import data.set.adaptator.TwoOfflineSet_To_PairOfflineSet;
import data.set.implementation.OfflineSet_Memory;
import data.vectors.DoubleVector;

public class TutorielPerceptron {

	public static void main(String[] args) throws IOException{


		String path2data = "data/movies/raw/";
		
		
		String dicoFile = path2data+"dico.txt";
		String corpusNumFile = path2data+"corpusNum.txt";
		String labelNumFile = path2data+"labelNum.txt";
		

		//================== 
		// chargement corpus par factory
		DocumentDirectoryOfflineSetFactory builder = new DocumentDirectoryOfflineSetFactory(path2data);
		Pair< OfflineSet<String>, OfflineSet<Integer> > c = builder.build();
		OfflineSet<String> corpus = c.getX();
		OfflineSet<Integer> labels = c.getY();
		
		
		//===================
		// exemple d'utilisation:
		// definition d'iterateur sur les documents 
		OfflineSetIterator<String> iter = corpus.inputsetiterator();

		
		// Afficher le premier document:
		iter.next();
		System.out.println(iter.getCurrentObject());

		//====================
		// construction du dictionnaire: soit à partir du corpus, soit à partir d'un fichier

		boolean standardSP = true; 
		File fd = new File(dicoFile);
		Dictionary_Memory<String> dico;
		StringProcessor sp;
		if(standardSP)
			sp = Dictionary_Mapper.standardStringProcessor();
		else{
			StringProcessor_FromStringProcessors sp2 = new StringProcessor_FromStringProcessors();
			sp2.add(new StringProcessor_RemoveLineAndSpace());
			sp2.add(new StringProcessor_LowerCase());
			sp2.add(new StringProcessor_RemovePunctuation());
			sp = sp2;
		}
			

		if(!fd.isFile()){
			System.out.println("Construction du dictionnaire à partir du corpus");
			
			DictionaryBuilderFromOfflineSet dicoBuilder = new DictionaryBuilderFromOfflineSet(corpus, sp);

			dico = dicoBuilder.build();

			DictionarySaver<String> dicoSaver = new DictionarySaver<String>(dicoFile, dico);
			dicoSaver.save();
		}
		else{
			System.out.println("Chargement du dictionnaire à partir du fichier:" +dicoFile);
			
			// Alternative -> Construction à partir d'un fichier
			DictionaryBuilderFromFile dicLoader = 
				new DictionaryBuilderFromFile(dicoFile);
			dico = dicLoader.build();
		}

		//=====================
		// transformation des documents sous forme numeriques
		// On choisit de le faire une fois pour toute...

		boolean slowOption = false;
		fd = new File(corpusNumFile); File fd2 = new File(labelNumFile);
		OfflineSet<DoubleVector> corpusNumerique;
		Dictionary_Mapper mapper = new Dictionary_Mapper(dico , sp);

		if(slowOption){
			System.out.println("Corpus numérique obtenu par mapping online");
			corpusNumerique = new OfflineSet_Mapper<String,DoubleVector>(mapper, corpus);
		}
		else if(!fd.isFile() || !fd2.isFile()){
			System.out.println("Corpus numérique obtenu par mapping offline");
			
			OfflineSet_Memory<DoubleVector> corpTMP = new OfflineSet_Memory<DoubleVector>();
			

			iter = corpus.inputsetiterator();
			int cpt = 0;
			while(iter.hasNext()){
				iter.next();
				corpTMP.add(cpt, mapper.map((iter.getCurrentObject())));
				cpt++;
			}

//			Iterator<DoubleVector> iterNum = corpTMP.iterator();
//			while(iterNum.hasNext()){
//				DoubleVector vec = iterNum.next();
//			}
			corpusNumerique = corpTMP;
			
			// sauvegarde
			OctaveSaver os = new OctaveSaver(corpusNumFile);
			os.saveOfflineSetDoubleVector(corpusNumerique, (int) dico.size());
			os = new OctaveSaver(labelNumFile);
			os.saveOSI(labels);
			
		}
		else{
			
			System.out.println("Corpus numérique obtenu par chargement de : "+corpusNumFile);
			
			NumericalOfflineSetFromOctaveFile ol = new NumericalOfflineSetFromOctaveFile(corpusNumFile, labelNumFile);
			Pair<OfflineSet<DoubleVector>, OfflineSet<Integer>> p = ol.build();
			corpusNumerique = p.getX();
		}

		// transformation des etiquettes en etiquettes numerique
		// On choisit de le faire à la volée... A partir d'un set + un mapper (cf ci-dessous)
		LabelMapper labelsMapper = new LabelMapper();

		OfflineSet_Mapper<Integer, Boolean> labelsNum = 
			new OfflineSet_Mapper<Integer, Boolean>(labelsMapper, labels);

		// reunificaiton des données et labels dans un set:

		OfflineSet<Pair<DoubleVector, Boolean> > dataLabeled = 
			new  TwoOfflineSet_To_PairOfflineSet<DoubleVector, Boolean>(corpusNumerique,labelsNum);

		//====================
		// Construction d'un classifieur
		int nIterations = 300;
		int nbUpdate = 1000;
		double step = 1e-2;
		double lambda = 1e-4;
		boolean weightDecay = true;

		PerceptronLoss_L2Regularized perceptron = 
			new PerceptronLoss_L2Regularized(
					new PerceptronParameters(nIterations, step, new HingeLoss(), lambda, nbUpdate, weightDecay)); 

		System.out.println("Apprentissage d'un perceptron");
		perceptron.learn(dataLabeled);

		//====================
		// Evaluation des performances

		BinaryClassifierEvaluation<DoubleVector> eval = 
			new BinaryClassifierEvaluation<DoubleVector>(perceptron, dataLabeled);

		double[] perf = eval.evaluate();
		System.out.println("Confusion Matrix:");
		System.out.println(String.format("|%4f|%4f|", perf[0],perf[1] ));
		System.out.println(String.format("|%4f|%4f|", perf[2],perf[3] ));


	}




}

class LabelMapper implements Mapper<Integer, Boolean>{

	public Boolean map(Integer from) {
		if(from.equals(1))
			return true;
		return false;
	}

}




